在应用程序中使用和再分发 Solaris Studio 库


Oracle/Solaris Studio 工程小组的 Steve Clamage 和 Darryl Gove
2011 年 3 月修订

Oracle Solaris Studio 软件套件提供了多个库,可以将这些库合并成一个应用程序以提供功能和减少开发时间。这些库可再分发,这就意味着它们可以随依赖于它们的应用程序免费分发。Solaris Studio 12.2 可再分发库和文件的完整列表可在 Oracle 技术网 Solaris Studio 网站上找到。

本文介绍再分发这些库以及维护依赖它们的应用程序的最佳实践。

问题

应用程序可以通过两种方式链接到库:

  • 静态链接将库中的代码与可执行文件组合成一个文件。优点在于可以显式知道运行时使用的库版本,并且不能更改。缺点在于如果必须修改库(比如要修复错误),就必须重新链接整个应用程序并提供一个新版本。

  • 动态链接假设库在应用程序之外并且将在运行时由应用程序加载。优点在于可以更新或替换库,而不必重新编译或重新链接应用程序,如果多个进程使用同一个库,则只需在这些进程之间共享一个副本,由此减少使用的物理内存。但也存在一些缺点:如果系统上有库的多个副本,应用程序可能会选择加载错误的库;万一应用程序所使用的库在应用程序运行时被更新,则可能出现无法预测的行为。

应优先选择动态链接,尤其是对于编译器提供的库,以及应用程序所要求的用户库。这样可以更好地管理库代码更新。

示例

此代码示例使用 C++ 标准库中的矢量类

示例 1 — 使用 C++ 标准库的代码示例

#include <vector>

int main()
{
   std::vector<int> v;
   v.push_back(10);
   return(0);
}

默认情况下,编译器将使用 C++ 标准库 libCstd,该库是作为 Solaris 的一部分提供的。此库作为操作系统的一部分安装在 /usr/lib 中,因此不需要单独打包。请使用 ldd 实用程序确定应用程序将链接到哪些库,如下所示。

示例 2 — 使用默认标准库

% CC v.cc 
% ldd a.out   
        libCstd.so.1 =>  /usr/lib/libCstd.so.1
        libCrun.so.1 =>  /usr/lib/libCrun.so.1
        libm.so.1 =>     /usr/lib/libm.so.1
        libc.so.1 =>     /usr/lib/libc.so.1
        libdl.so.1 =>    /usr/lib/libdl.so.1
        /usr/lib/cpu/sparcv8plus/libCstd_isa.so.1
        /usr/platform/SUNW,Sun-Fire-880/lib/libc_psr.so.1

开发人员可能决定使用替代的 stlport4 库,与默认库相比,该库更符合标准,且通常可提供更好的性能。此库不是随 Oracle Solaris 一起提供的,而是作为 Solaris Studio 分发的一部分提供的。下一示例显示使用标志 -library=stlport4 编译的同一代码,该标志告诉编译器使用 stlport4 库而不是默认的 libCstd。来自 ldd 的输出显示应用程序链接到位于作为 Solaris Studio 分发一部分的目录中的库。

示例 3 — 使用 stlport4

% CC -library=stlport4 v.cc

% ldd a.out  
        libstlport.so.1 => /opt/SUNWspro/lib/stlport4/libstlport.so.1
        librt.so.1 =>    /usr/lib/librt.so.1
        libCrun.so.1 =>  /usr/lib/libCrun.so.1
        libm.so.1 =>     /usr/lib/libm.so.1
        libc.so.1 =>     /usr/lib/libc.so.1
        libaio.so.1 =>   /usr/lib/libaio.so.1
        libmd5.so.1 =>   /usr/lib/libmd5.so.1
        libdl.so.1 =>    /usr/lib/libdl.so.1
        /usr/platform/SUNW,Sun-Fire-880/lib/libc_psr.so.1
        /usr/platform/SUNW,Sun-Fire-880/lib/libmd5_psr.so.1

在未安装 Solaris Studio 软件的另一系统上运行此程序,链接器将无法找到文件 libstlport.so.1。这种情况显示在下一示例中,输出来自未安装 Solaris Studio 的系统上的 ldd

示例 4 — 无法找到库的示例

% ldd a.out 
        libstlport.so.1 =>       (file not found)
        librt.so.1 =>    /usr/lib/librt.so.1
        libCrun.so.1 =>  /usr/lib/libCrun.so.1
        libm.so.1 =>     /usr/lib/libm.so.1
        libc.so.1 =>     /usr/lib/libc.so.1
        libaio.so.1 =>   /usr/lib/libaio.so.1
        libmd.so.1 =>    /usr/lib/libmd.so.1
        libm.so.2 =>     /usr/lib/libm.so.2
        /platform/SUNW,Sun-Fire-T200/lib/libc_psr.so.1
        /platform/SUNW,Sun-Fire-T200/lib/libmd_psr.so.1

针对这种情况的一种比较常用的变通方法是将环境变量 LD_LIBRARY_PATH 设置为指向包含所缺库的目录。尽管这种方法确实有用,但不建议使用这种修复办法,因为这种方法很脆弱,并且要求正确设置用户的环境。

例如,库 libstlport.so.1 可能已被复制到当前目录,并且 LD_LIBRARY_PATH 已设置为“.”(句点)。下一示例显示了这种情况。但如果是从当前目录以外的任何目录调用应用程序,该应用程序将无法找到 stlport4 库。

示例 5 — 不建议使用设置 LD_LIBRARY_PATH 的变通方法

% cd test 
% export LD_LIBRARY_PATH=.  
% ldd a.out 
        libstlport.so.1 =>   ./libstlport.so.1
        librt.so.1 =>    /usr/lib/librt.so.1
        libCrun.so.1 =>  /usr/lib/libCrun.so.1
        libm.so.1 =>     /usr/lib/libm.so.1
        libc.so.1 =>     /usr/lib/libc.so.1
        libaio.so.1 =>   /usr/lib/libaio.so.1
        libmd.so.1 =>    /usr/lib/libmd.so.1
        libm.so.2 =>     /usr/lib/libm.so.2
        /platform/SUNW,Sun-Fire-T200/lib/libc_psr.so.1
        /platform/SUNW,Sun-Fire-T200/lib/libmd_psr.so.1
% cd .. 
% ldd test/a.out   
        libstlport.so.1 =>       (file not found)
        librt.so.1 =>    /usr/lib/librt.so.1
        libCrun.so.1 =>  /usr/lib/libCrun.so.1
        libm.so.1 =>     /usr/lib/libm.so.1
        libc.so.1 =>     /usr/lib/libc.so.1
        libaio.so.1 =>   /usr/lib/libaio.so.1
        libmd.so.1 =>    /usr/lib/libmd.so.1
        libm.so.2 =>     /usr/lib/libm.so.2
        /platform/SUNW,Sun-Fire-T200/lib/libc_psr.so.1
        /platform/SUNW,Sun-Fire-T200/lib/libmd_psr.so.1

LD_LIBRARY_PATH 环境变量对于特殊测试很有用,但对于部署的程序,它不是一个可伸缩或可维护的解决方案。Rod Evans 在其博客“对 LD_LIBRARY_PATH说不”中进行了详细的讨论。

分发共享库的正确方式

通过将应用程序二进制文件与目录结构中的任何其他库一起打包来避免使用 LD_LIBRARY_PATH 环境变量,如下面的示例所示。

示例 6 — 建议的应用程序目录结构

/application
            /bin       Contains executables
            /lib       Contains necessary libraries

在该示例中,stlport.so.1 库将被复制到 /lib 子目录中。编译器标志 -library=stlport4 将使得在构建时链接 stlport4 库而不是默认库。通过使用 -R dir 选项进行编译,链接器在运行时将在应用程序的 /lib 子目录中查找库。

尽管您可以指定在一个绝对目录路径中搜索库,但这将限制安装到文件系统中的特定位置,同样还需要使用 LD_LIBRARY_PATH 作为一种我们不希望使用的变通方法。更好的办法是结合使用令牌 $ORIGIN-R 选项,告诉应用程序在相对于可执行文件位置的路径中查找。$ORIGIN 令牌可能需要特殊处理以避免被 shell 解释。下图显示了这一情况。

示例 7 — 为库指定相对运行时路径

% CC -library=stlport4 -R'$ORIGIN/../lib' v.cc

在 Makefile 中,需要额外的 $ 转义符来避免 $ORIGIN 被解释成 Make 变量。下一示例显示了这种情况。

示例 8 — 在 Makefile 中指定相对运行时路径

v: v.cc        CC -library=stlport4 -R \$$ORIGIN/../lib v.cc -o v

在目标计算机上,这将导致应用程序在应用程序的 /lib 目录中查找库,如下面示例中所示。

示例 9 — 在相对目录路径中查找库

% ldd a.out 
        libstlport.so.1 => /export/home/test/bin/../lib/libstlport.so.1
        librt.so.1 =>    /usr/lib/librt.so.1
        libCrun.so.1 =>  /usr/lib/libCrun.so.1
        libm.so.1 =>     /usr/lib/libm.so.1
        libc.so.1 =>     /usr/lib/libc.so.1
        libaio.so.1 =>   /usr/lib/libaio.so.1
        libmd.so.1 =>    /usr/lib/libmd.so.1
        libm.so.2 =>     /usr/lib/libm.so.2
        /platform/SUNW,Sun-Fire-T200/lib/libc_psr.so.1
        /platform/SUNW,Sun-Fire-T200/lib/libmd_psr.so.1

总结

由于以下原因,建议结合使用 $ORIGIN 令牌和 -R 选项在相对于可执行文件的相对路径上查找库:

  • 执行文件和库可以放在同一位置,这可以确保可执行文件与相应的库版本一起分发,并使用相应的库版本。

  • 如果更新了支持库,很容易复制新的更新版本来替换早期版本。

  • 每个库由一个应用程序(或一个应用程序系列)使用,因此可以在不危及系统上安装的其他应用程序的情况下更新库的当前版本。

  • 应用程序和库可以安装在任意位置,并且可以放心地正常工作,无需用户使用像 LD_LIBRARY_PATH 这样的变通方法。

  • 本文所介绍的方法同样适用于第三方共享库或作为应用程序的一部分而创建的库。

作者

Steve Clamage 自 1994 年起就在 Sun 工作,目前是 Oracle Solaris Studio C++ 编译器的技术负责人。他从 1995 年开始一直担任 ANSI C++ 委员会的主席。

Darryl Gove 是 Oracle 的 Solaris Studio 编译器性能工程小组的高级工程师,负责分析和优化应用程序在当前和未来 UltraSPARC 系统上的性能。Darryl 还是《Solaris Application Programming》(PrenticeHall 2008).Hall 2008)一书的作者。在此阅读 Darryl 的博客。